#include "raster.h"
#include "raster-3d.h"
#include "raster-triangles.h"
#include "raster-channels.h"

BEST_INLINE int is_ccw(RasterVert *verts)
{
    int ret = FixedToInt(verts[1].x - verts[0].x) * FixedToInt(verts[1].y + verts[0].y);
    ret += FixedToInt(verts[2].x - verts[1].x) * FixedToInt(verts[2].y + verts[1].y);
    ret += FixedToInt(verts[0].x - verts[2].x) * FixedToInt(verts[0].y + verts[2].y);
    return ret > 0;
}

BEST_INLINE int _HighestVert(RasterVert *verts)
{
    if(verts[0].y > verts[1].y)
    {
        if(verts[1].y > verts[2].y)
            return 2;
        return 1;
    }
    if(verts[0].y > verts[2].y)
        return 2;
    return 0;
}

BEST_INLINE void _ProjectVert(RasterVert *vert)
{
    Fixed invz = FixedDiv(FIXED_ONE, vert->z);
    vert->x = FixedMul(vert->x, invz);
    vert->y = FixedMul(vert->y, invz);
}
/* for perspective correction */
BEST_INLINE void _ProjectVertAndUVW(RasterVert *vert)
{
    Fixed invz = FixedDiv(FIXED_ONE, vert->z);
    vert->x = FixedMul(vert->x, invz);
    vert->y = FixedMul(vert->y, invz);
    vert->u = FixedMul(vert->u, invz);
    vert->v = FixedMul(vert->v, invz);
    vert->w = FixedMul(vert->w, invz);
}

/* precondition: edges shall be built in CCW order */
static void _DoTriangle3D(Raster *raster, Edge *edges, SpanFn fn, void *args)
{
    if(edges[0].y == edges[1].y)
    {
        SpansBetween(raster, &edges[0], &edges[1], fn, args);
        if(edges[0].y == edges[2].y) /* can draw final edge too */
        {
            if(edges[0].h != 0)
                SpansBetween(raster, &edges[0], &edges[2], fn, args);
            else
                SpansBetween(raster, &edges[1], &edges[2], fn, args);
        }
    }
    else
    {
        SpansBetween(raster, &edges[0], &edges[2], fn, args);
        if(edges[0].y == edges[1].y) /* can draw final edge too */
        {
            if(edges[0].h != 0)
                SpansBetween(raster, &edges[0], &edges[1], fn, args);
            else
                SpansBetween(raster, &edges[2], &edges[1], fn, args);
        }
    }
}

BEST_INLINE void _SequentialApplyOpaque(Pixel *first, Pixel *last, Pixel color)
{
    while(first < last)
    {
        *first = color;
        ++first;
    }
}

BEST_INLINE void _SequentialApplyAlpha(Pixel *first, Pixel *last, Pixel color)
{
    while(first < last)
    {
        *first = AlphaBlendPixels(*first, color);
        ++first;
    }
}

static int __cdecl _SpansBetweenTriangleOpaque(Raster *raster, Edge *edge0, Edge *edge1, void *args)
{
    int x0 = FixedToInt(edge0->x);
    int x1 = FixedToInt(edge1->x);
    Pixel *line = raster->pixels + (edge0->y * raster->pitch);
    Pixel color = *(Pixel *)(args);

    if(x0 < x1)
    {
        ClipSpan(&raster->clipRect, &x0, &x1);
        _SequentialApplyOpaque(line + x0, line + x1, color);
    }
    return 0;
}
static int __cdecl _SpansBetweenTriangleAlpha(Raster *raster, Edge *edge0, Edge *edge1, void *args)
{
    int x0 = FixedToInt(edge0->x);
    int x1 = FixedToInt(edge1->x);
    Pixel *line = raster->pixels + (edge0->y * raster->pitch);
    Pixel color = *(Pixel *)(args);

    if(x0 < x1)
    {
        ClipSpan(&raster->clipRect, &x0, &x1);
        _SequentialApplyAlpha(line + x0, line + x1, color);
    }
    else if(x0 != x1)
    {
        return -1;
    }
    return 0;
}

BEST_INLINE void PrepareEdge3D(Edge *edge, RasterVert *pt1, RasterVert *pt2)
{
    PrepareEdge(edge, pt1, pt2);
}

void R3D_TriangleCCW(Raster *raster, RasterVert *verts, Pixel color)
{
    RasterVert wverts[3] = { verts[0], verts[1], verts[2] };
    Edge edges[3];
    int topvert;
    SpanFn fn;
    /* backface culling */
    _ProjectVert(&wverts[0]);
    _ProjectVert(&wverts[1]);
    _ProjectVert(&wverts[2]);
    if(!is_ccw(verts))
        return;
    topvert = _HighestVert(verts);
    PrepareEdge3D(&edges[0], &wverts[topvert], &wverts[(topvert + 1)% 3]);
    PrepareEdge3D(&edges[1], &wverts[(topvert + 1)% 3], &wverts[(topvert + 2)% 3]);
    PrepareEdge3D(&edges[2], &wverts[topvert], &wverts[(topvert + 2)% 3]);

    if(IsPixelOpaque(color))
        fn = _SpansBetweenTriangleOpaque;
    else
        fn = _SpansBetweenTriangleAlpha;
    _DoTriangle3D(raster, edges, fn, &color);
}
void R3D_TriangleCW(Raster *raster, RasterVert *verts, Pixel color)
{
    RasterVert wverts[3] = { verts[0], verts[1], verts[2] };
    Edge edges[3];
    int topvert;
    SpanFn fn;
    /* backface culling */
    _ProjectVert(&wverts[0]);
    _ProjectVert(&wverts[1]);
    _ProjectVert(&wverts[2]);
    if(is_ccw(verts))
        return;
    topvert = _HighestVert(verts);
    PrepareEdge3D(&edges[0], &wverts[topvert], &wverts[(topvert + 2)% 3]);
    PrepareEdge3D(&edges[1], &wverts[(topvert + 2)% 3], &wverts[(topvert + 1)% 3]);
    PrepareEdge3D(&edges[2], &wverts[topvert], &wverts[(topvert + 1)% 3]);

    if(IsPixelOpaque(color))
        fn = _SpansBetweenTriangleOpaque;
    else
        fn = _SpansBetweenTriangleAlpha;
    _DoTriangle3D(raster, edges, fn, &color);
}

struct TriangleBlitArgs
{
    const RasterFormat *fmt;
    Raster *src;
    BlitMode *mode;
};

static int __cdecl _SpansBetweenTriangleTex(Raster *raster, Edge *edge0, Edge *edge1, void *args)
{
    RasterSpan span;
    int lineBlitRet = 0;
    span.u0 = edge0->u;
    span.v0 = edge0->v;
    span.x0 = edge0->x;
    span.w0 = edge0->w;
    span.u1 = edge1->u;
    span.v1 = edge1->v;
    span.x1 = edge1->x;
    span.w1 = edge1->w;
    if(ClipSpan2(&raster->clipRect, &span) != 0)
    {
        struct TriangleBlitArgs *arg = (struct TriangleBlitArgs *)args;
        lineBlitRet = arg->fmt->SpanBlit(raster, arg->src, edge0->y, &span, arg->mode);
    }
    return lineBlitRet;
}



void R3D_TriangleTexCCW(Raster *raster, Raster *src, RasterVert *verts, BlitMode *mode)
{
    RasterVert wverts[3] = { verts[0], verts[1], verts[2] };
    Edge edges[3];
    int topvert;
    struct TriangleBlitArgs args;
    const RasterFormat fmt = LookupFormat(src->fmt);
    if(mode && mode->perspective_correction == 0) /* for affine mapping, we can't project UV */
    {
        _ProjectVert(&wverts[0]);
        _ProjectVert(&wverts[1]);
        _ProjectVert(&wverts[2]);
    }
    else
    {
        _ProjectVertAndUVW(&wverts[0]);
        _ProjectVertAndUVW(&wverts[1]);
        _ProjectVertAndUVW(&wverts[2]);
    }
    /* backface culling */
    if(!is_ccw(wverts))
        return;
    topvert = _HighestVert(wverts);
    PrepareEdge3D(&edges[0], &wverts[topvert], &wverts[(topvert + 1)% 3]);
    PrepareEdge3D(&edges[1], &wverts[(topvert + 1)% 3], &wverts[(topvert + 2)% 3]);
    PrepareEdge3D(&edges[2], &wverts[topvert], &wverts[(topvert + 2)% 3]);

    args.fmt = &fmt;
    args.src = src;
    args.mode = mode;
    _DoTriangle3D(raster, edges, _SpansBetweenTriangleTex, &args);
}
void R3D_TriangleTexCW(Raster *raster, Raster *src, RasterVert *verts, BlitMode *mode)
{
    RasterVert wverts[3] = { verts[0], verts[1], verts[2] };
    Edge edges[3];
    int topvert;
    struct TriangleBlitArgs args;
    const RasterFormat fmt = LookupFormat(src->fmt);
    if(mode && mode->perspective_correction == 0) /* for affine mapping, we can't project UV */
    {
        _ProjectVert(&wverts[0]);
        _ProjectVert(&wverts[1]);
        _ProjectVert(&wverts[2]);
    }
    else
    {
        _ProjectVertAndUVW(&wverts[0]);
        _ProjectVertAndUVW(&wverts[1]);
        _ProjectVertAndUVW(&wverts[2]);
    }
    /* backface culling */
    if(is_ccw(verts))
        return;
    topvert = _HighestVert(verts);
    PrepareEdge3D(&edges[0], &wverts[topvert], &wverts[(topvert + 2)% 3]);
    PrepareEdge3D(&edges[1], &wverts[(topvert + 2)% 3], &wverts[(topvert + 1)% 3]);
    PrepareEdge3D(&edges[2], &wverts[topvert], &wverts[(topvert + 1)% 3]);

    args.fmt = &fmt;
    args.src = src;
    args.mode = mode;
    _DoTriangle3D(raster, edges, _SpansBetweenTriangleTex, &args);
}
